/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.support;

import java.awt.Canvas;
import java.awt.Frame;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.Custom;
import fr.x9c.cadmium.kernel.Interpreter;
import fr.x9c.cadmium.kernel.Value;
import fr.x9c.cadmium.primitives.graph.CustomImage;
import fr.x9c.cadmium.primitives.graph.GraphSlot;

/**
 * This class provides utility methods easing
 * {@link fr.x9c.cadmium.kernel.Value} manipulation, as well as retrieval of
 * GUI elements. <br/>
 * One should notice that is the responsibility of the programmer to ensure
 * that the correct type is used. <br/>
 * For example, <tt>createInt(0)</tt> creates a value that can be any of
 * the following:
 * <ul>
 *   <li><i>()</i></li>
 *   <li><i>[]</i></li>
 *   <li><i>0</i></li>
 *   <li><i>A</i> with <i>type t = A | ...</i></li>
 *   <li>...</li>
 * </ul>
 * <br/>
 * Informations about value encoding can be found in the OCaml user manual,
 * chapter 18 titled "Interfacing C with Objective Caml".
 *
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
public final class Helper {

    /**
     * No instance of this class.
     */
    private Helper() {
    } // end empty constructor

    /**
     * Creates a value of OCaml <i>int</i> type.
     * @param x value - should be between {@link fr.x9c.cadmium.kernel.Value#MIN_LONG}
     * and {@link fr.x9c.cadmium.kernel.Value#MAX_LONG}.
     * @return OCaml value
     */
    public static Value createInt(final int x) {
        assert (x >= Value.MIN_LONG) && (x <= Value.MAX_LONG) : "invalid x";
        return Value.createFromLong(x);
    } // end method 'createInt(int)'

    /**
     * Converts an OCaml value of type <i>int</i> into its actual value.
     * @param x OCaml value
     * @return actual value
     */
    public static int readInt(final Value x) {
        assert x != null : "null x";
        return x.asLong();
    } // end method 'readInt(Value)'

    /**
     * Creates a value of OCaml <i>int32</i> type.
     * @param x value
     * @return OCaml value
     */
    public static Value createInt32(final int x) {
        final Block res = Block.createCustom(Custom.INT_32_SIZE,
                                             Custom.INT_32_OPS);
        res.setInt32(x);
        return Value.createFromBlock(res);
    } // end method 'createInt32(int)'

    /**
     * Converts an OCaml value of type <i>int32</i> into its actual value.
     * @param x OCaml value
     * @return actual value
     */
    public static int readInt32(final Value x) {
        assert x != null : "null x";
        return x.asBlock().asInt32();
    } // end method 'readInt32(Value)'

    /**
     * Creates a value of OCaml <i>nativeint</i> type.
     * @param x value
     * @return OCaml value
     */
    public static Value createNativeInt(final int x) {
        final Block res = Block.createCustom(Custom.INT_NAT_SIZE,
                                             Custom.INT_NAT_OPS);
        res.setNativeInt(x);
        return Value.createFromBlock(res);
    } // end method 'createNativeInt(int)'

    /**
     * Converts an OCaml value of type <i>nativeint</i> into its actual value.
     * @param x OCaml value
     * @return actual value
     */
    public static int readNativeInt(final Value x) {
        assert x != null : "null x";
        return x.asBlock().asNativeInt();
    } // end method 'readNativeInt(Value)'

    /**
     * Creates a value of OCaml <i>int64</i> type.
     * @param x value
     * @return OCaml value
     */
    public static Value createInt64(final long x) {
        final Block res = Block.createCustom(Custom.INT_64_SIZE,
                                             Custom.INT_64_OPS);
        res.setInt64(x);
        return Value.createFromBlock(res);
    } // end method 'createInt64(long)'

    /**
     * Converts an OCaml value of type <i>int64</i> into its actual value.
     * @param x OCaml value
     * @return actual value
     */
    public static long readInt64(final Value x) {
        assert x != null : "null x";
        return x.asBlock().asInt64();
    } // end method 'readInt64(Value)'

    /**
     * Creates a value of OCaml <i>float</i> type.
     * @param x value
     * @return OCaml value
     */
    public static Value createFloat(final double x) {
        return Value.createFromBlock(Block.createDouble(x));
    } // end method 'createFloat(double)'

    /**
     * Converts an OCaml value of type <i>float</i> into its actual value.
     * @param x OCaml value
     * @return actual value
     */
    public static double readFloat(final Value x) {
        assert x != null : "null x";
        return x.asBlock().asDouble();
    } // end method 'readFloat(Value)'

    /**
     * Creates a value of OCaml <i>string</i> type.
     * @param x value
     * @return OCaml value
     */
    public static Value createString(final String x) {
        assert x != null : "null x";
        return Value.createFromBlock(Block.createString(x));
    } // end method 'createString(String)'

    /**
     * Converts an OCaml value of type <i>string</i> into its actual value.
     * @param x OCaml value
     * @return actual value
     */
    public static String readString(final Value x) {
        assert x != null : "null x";
        return x.asBlock().asString();
    } // end method 'readString(Value)'

    /**
     * Creates an array of floats.
     * @param x float values - should not be <tt>null</tt>
     * @return float array
     */
    public static Value createFloatArray(final double... x) {
        assert x != null : "null x";
        return Value.createFromBlock(Block.createDoubleArray(x));
    } // end method 'createFloatArray(double...)'

    /**
     * Converts an array of floats into its actual value.
     * @param x array of floats - should not be <tt>null</tt>
     * @return actual value
     */
    public static double[] readFloatArray(final Value x) {
        assert x != null : "null x";
        final Block b = x.asBlock();
        final int len = b.sizeDoubles();
        final double[] res = new double[len];
        for (int i = 0; i < len; i++) {
            res[i] = b.getDouble(i);
        } // end for
        return res;
    } // end method 'readFloatArray(Value)'

    /**
     * Creates a tuple of floats. <br/>
     * Exact synonym of {@link #createFloatArray(double...)}.
     * @param x float values - should not be <tt>null</tt>
     * @return float tuple
     */
    public static Value createFloatTuple(final double... x) {
        assert x != null : "null x";
        return createFloatArray(x);
    } // end method 'createFloatTuple(double...)'

    /**
     * Converts a tuple of floats into its actual value. <br/>
     * Exact synonym of {@link #readFloatArray(Value)}.
     * @param x tuple of floats - should not be <tt>null</tt>
     * @return actual value
     */
    public static double[] readFloatTuple(final Value x) {
        assert x != null : "null x";
        return readFloatArray(x);
    } // end method 'readFloatTuple(Value)'

    /**
     * Gets a float from an array or tuple of floats.
     * @param x array or tuple of floats - should not be <tt>null</tt>
     * @param i index of float to get
     * @return float at index <tt>i</tt> in <tt>x</tt>
     */
    public static double getFloat(final Value x, final int i) {
        assert x != null : "null x";
        return x.asBlock().getDouble(i);
    } // end method 'getFloat(Value)'

    /**
     * Sets a float in an array or tuple of floats.
     * @param x array or tuple of floats - should not be <tt>null</tt>
     * @param i index of float to set
     * @param d new float value
     */
    public static void setFloat(final Value x, final int i, final double d) {
        assert x != null : "null x";
        x.asBlock().setDouble(i, d);
    } // end method 'setFloat(Value, int, double)'

    /**
     * Creates an array of values.
     * @param x values - should not be <tt>null</tt>
     * @return array
     */
    public static Value createArray(final Value... x) {
        assert x != null : "null x";
        final int len = x.length;
        final Block res = Block.createBlock(len, 0);
        for (int i = 0; i < len; i++) {
            res.set(i, x[i]);
        } // end for
        return Value.createFromBlock(res);
    } // end method 'createArray(Value...)'

    /**
     * Converts an array of values into its actual value.
     * @param x array of values - should not be <tt>null</tt>
     * @return actual value
     */
    public static Value[] readArray(final Value x) {
        assert x != null : "null x";
        final Block b = x.asBlock();
        final int len = b.sizeValues();
        final Value[] res = new Value[len];
        for (int i = 0; i < len; i++) {
            res[i] = b.get(i);
        } // end for
        return res;
    } // end method 'readArray(Value)'

    /**
     * Creates a tuple of values. <br/>
     * Exact synonym of {@link #createArray(Value...)}.
     * @param x values - should not be <tt>null</tt>
     * @return tuple
     */
    public static Value createTuple(final Value... x) {
        assert x != null : "null x";
        return createArray(x);
    } // end method 'createTuple(Value...)'

    /**
     * Converts a tuple of values into its actual value. <br/>
     * Exact synonym of {@link #readArray(Value)}.
     * @param x tuple of values - should not be <tt>null</tt>
     * @return actual value
     */
    public static Value[] readTuple(final Value x) {
        assert x != null : "null x";
        return readArray(x);
    } // end method 'readTuple(Value)'

    /**
     * Gets a value from an array or tuple.
     * @param x array or tuple - should not be <tt>null</tt>
     * @param i index of value to get
     * @return value at index <tt>i</tt> in <tt>x</tt>
     */
    public static Value get(final Value x, final int i) {
        assert x != null : "null x";
        return x.asBlock().get(i);
    } // end method 'get(Value, int)'

    /**
     * Sets a value in an array or tuple.
     * @param x array or tuple - should not be <tt>null</tt>
     * @param i index of value to set
     * @param v new value - should not be <tt>null</tt>
     */
    public static void set(final Value x, final int i, final Value v) {
        assert x != null : "null x";
        assert v != null : "null v";
        x.asBlock().set(i, v);
    } // end method 'set(Value, int, Value)'

    /**
     * Creates a value of OCaml <i>'a list</i> type. <br/>
     * It is the responsibility of the programmer to populate the list with
     * elements of the same type.
     * @param x list elements - should not be <tt>null</tt>
     * @return OCaml list
     */
    public static Value createList(final Value... x) {
        assert x != null : "null x";
        Value res = Value.EMPTY_LIST;
        final int len = x.length;
        for (int i = len - 1; i >= 0; i--) {
            final Block cons = Block.createBlock(Block.TAG_CONS, x[i], res);
            res = Value.createFromBlock(cons);
        } // end for
        return res;
    } // end method 'createList(Value...)'

    /**
     * Creates a value of OCaml <i>'a list</i> type. <br/>
     * It is the responsibility of the programmer to populate the list with
     * elements of the same type.
     * @param x list elements - should not be <tt>null</tt>
     * @return OCaml list
     */
    public static Value createList(final List<Value> x) {
        assert x != null : "null x";
        final ListIterator<Value> it = x.listIterator(x.size());
        Value res = Value.EMPTY_LIST;
        while (it.hasPrevious()) {
            final Value v = it.previous();
            final Block cons = Block.createBlock(Block.TAG_CONS, v, res);
            res = Value.createFromBlock(cons);
        } // end for
        return res;
    } // end method 'createList(List<Value>)'

    /**
     * Converts an OCaml list into a Java one.
     * @param x OCaml list - should not be <tt>null</tt>
     * @return Java list
     */
    public static List<Value> readList(final Value x) {
        assert x != null : "null x";
        final List<Value> res = new LinkedList<Value>();
        Value list = x;
        while (list != Value.EMPTY_LIST) {
            final Block b = list.asBlock();
            res.add(b.get(0));
            list = b.get(1);
        } // end while
        return res;
    } // end method 'readList(Value)'

    /**
     * Duplicates an OCaml value from an interpreter to another one. <br/>
     * <b>Should be used with great care as passing a closure (or any value
     * containing a code offset) to an interpreter executing a different code
     * will yield unpredictable results.</b> <br/>
     * Block values that are not atoms are shared and thus block values with
     * registered finalizers should not be duplicated.
     * @param x value to duplicate - should not be <tt>null</tt>
     * @param from source interpreter - should not be <tt>null</tt>
     * @param to destination interpreter - should not be <tt>null</tt>
     * @return duplicated value
     */
    public static Value duplicate(final Value x,
                                  final Interpreter from,
                                  final Interpreter to) {
        assert x != null : "null x";
        assert from != null : "null from";
        assert to != null : "null to";

        if (x.isBlock()) {
            // Atoms insides x are not converted but this should not be a major
            // issue. Anyway, we do not want to traverse the whole x value.
            if (from.getContext().isAtom(x)) {
                return to.getContext().getAtom(x.asBlock().getTag());
            } else {
                return Value.createFromBlock(x.asBlock().duplicate());
            } // end if/else
        } else {
            return x;
        } // end if/else
    } // end method 'duplicate(Value, Interpreter, Interpreter)'

    /**
     * Creates a value of OCaml <i>_image</i> type
     * (<i>Graphics</i> library). <br/>
     * The image is encapsulated and not copied.
     * @param bi image to encapsulate - should not be <tt>null</tt>
     * @return OCaml value
     */
    public static Value createImage(final BufferedImage bi) {
        assert bi != null : "null bi";
        final Block res = Block.createCustom(CustomImage.SIZE,
                                             CustomImage.OPS);
        res.setCustom(bi);
        return Value.createFromBlock(res);
    } // end method 'toValue(BufferedImage)'

    /**
     * Converts an OCaml value of type <i>_image</i> into a Java image.
     * @param x OCaml value - should not be <tt>null</tt>
     * @return the encapsulated Java image
     */
    public static BufferedImage readImage(final Value x) {
        assert x != null : "null x";
        return (BufferedImage) x.asBlock().asCustom();
    } // end method 'toBufferedImage(Value)'

    /**
     * Returns the <i>Graphics</i> frame of an interpreter.
     * @param interp interpreter to get frame from - should not be <tt>null</tt>
     * @return the <i>Graphics</i> frame of the passed interpreter,
     *         <tt>null</tt> if the <i>Graphics</i> library has not been
     *         opened or is canvas-only
     */
    public static Frame getFrame(final Interpreter interp) {
        assert interp != null : "null interp";
        final GraphSlot slot =
            (GraphSlot) interp.getContext().getSlot(GraphSlot.SLOT);
        return slot != null
            ? slot.getFrame()
            : null;
    } // end method 'getFrame()'

    /**
     * Returns the <i>Graphics</i> canvas of an interpreter.
     * @param interp interpreter to get canvas from - should not be <tt>null</tt>
     * @return the <i>Graphics</i> canvas of the passed interpreter,
     *         <tt>null</tt> if the <i>Graphics</i> library has not been
     *         opened
     */
    public static Canvas getCanvas(final Interpreter interp) {
        assert interp != null : "null interp";
        final GraphSlot slot =
            (GraphSlot) interp.getContext().getSlot(GraphSlot.SLOT);
        return slot != null
            ? slot.getCanvas()
            : null;
    } // end method 'getCanvas()'

    /**
     * Returns the <i>Graphics</i> buffer of an interpreter. <br/>
     * One should notice that a new buffer is created each time the frame is
     * resized.
     * @param interp interpreter to get buffer from - should not be <tt>null</tt>
     * @return the <i>Graphics</i> buffer of the passed interpreter,
     *         <tt>null</tt> if the <i>Graphics</i> library has not been
     *         opened
     */
    public static BufferedImage getBuffer(final Interpreter interp) {
        assert interp != null : "null interp";
        final GraphSlot slot =
            (GraphSlot) interp.getContext().getSlot(GraphSlot.SLOT);
        return slot != null
            ? slot.getBuffer()
            : null;
    } // end method 'getBuffer()'

} // end class 'Helper'
